using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Calimbra.Runtime.Contracts;

namespace Calimbra.Runtime.Functions
{
    public class Split<TInput, TOutput1, TOutput2> : ISplit<TInput, TOutput1, TOutput2>
    {
        private readonly Func<TInput, TOutput1> factory1;
        private readonly Func<TInput, TOutput2> factory2;

        public Split() {
            if (typeof(TOutput1) == typeof(TInput)) {
                factory1 = x => (TOutput1)(object)x;
            }
            if (typeof(TOutput2) == typeof(TInput)) {
                factory2 = x => (TOutput2)(object)x;
            }

            var getter1Name = "";
            if (factory1 == null) {
                var getter1 = GetGettersOfType(typeof(TOutput1))
                    .First()
                    .GetGetMethod(true);
                factory1 = x => (TOutput1)getter1.Invoke(x, new object[] {});
                getter1Name = getter1.Name;
            }

            if (factory2 == null) {
                var getter2 = GetGettersOfType(typeof(TOutput2))
                    .Where(property => property.GetGetMethod(true).Name != getter1Name)
                    .First()
                    .GetGetMethod(true);
                factory2 = x => (TOutput2)getter2.Invoke(x, new object[] {});
            }
        }

        private static IEnumerable<PropertyInfo> GetGettersOfType(Type type) {
            return typeof(TInput).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(property => property.PropertyType == type);
        }

        public Split(Func<TInput, TOutput1> factory1, Func<TInput, TOutput2> factory2) {
            this.factory1 = factory1;
            this.factory2 = factory2;
        }

        public void Input(TInput message) {
            Output1(factory1(message));
            Output2(factory2(message));
        }

        public event Action<TOutput1> Output1;

        public event Action<TOutput2> Output2;
    }

    public class Split<TInput, TOutput1> : ISplit<TInput, TOutput1>
    {
        private readonly Func<TInput, TOutput1> factory1;

        public Split()
        {
            if (typeof(TOutput1) == typeof(TInput))
            {
                factory1 = x => (TOutput1)(object)x;
            }

            if (factory1 == null)
            {
                var getter1 = GetGettersOfType(typeof(TOutput1))
                    .First()
                    .GetGetMethod(true);
                factory1 = x => (TOutput1)getter1.Invoke(x, new object[] { });
            }

        }

        private static IEnumerable<PropertyInfo> GetGettersOfType(Type type)
        {
            return typeof(TInput).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(property => property.PropertyType == type);
        }

        public Split(Func<TInput, TOutput1> factory1)
        {
            this.factory1 = factory1;
        }

        public void Input(TInput message)
        {
            Output1(factory1(message));
            Output2();
        }

        public event Action<TOutput1> Output1;

        public event Action Output2;
    }

    public class Split : ISplit
    {
        public void Input() {
            Output1();
            Output2();
        }

        public event Action Output1;
        public event Action Output2;
    }
}